package com.lagou.factory;

import com.lagou.anno.Autowired;
import com.lagou.anno.Bean;
import com.lagou.anno.ComponentScan;
import com.lagou.anno.Configuration;
import com.lagou.anno.PropertySource;
import com.lagou.anno.Service;
import com.lagou.anno.Value;
import com.lagou.config.BeanDefinition;
import com.lagou.config.BeanDefinitionProcess;
import com.lagou.config.DependInfo;
import com.lagou.utils.GenericTokenParser;
import com.lagou.utils.ResolverUtil;
import com.lagou.utils.TokenHandler;
import org.apache.log4j.Logger;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.function.Consumer;

/**
 * @author: wudong
 * @create: 2021-05-26 14:06
 **/
public class AnnoApplicatonBeanFacotry extends ApplicatonBeanFacotry {
    private static final Logger logger = Logger.getLogger(AnnoApplicatonBeanFacotry.class);

    private final Class<?> configCalss;
    @Value(value = "",required = true)
    private Object config;
    private Properties properties = new Properties();

    public AnnoApplicatonBeanFacotry(Class<?> configCalss) {
        this.configCalss = configCalss;
    }

    public void prepare() {
        try {
            Configuration configuration = configCalss.getAnnotation(Configuration.class);
            if (configuration == null) {
                throw new RuntimeException("配置类没有使用@Configuration注解:" + configCalss);
            }

            //读取配置文件
            loadProperties();

            //初始化配置对象
            initConfig();

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    protected void initBeanDefiniton(Consumer<BeanDefinition> registry) {
        initBeanDefinitonInConfig(registry);
        initBeanDefinitonInpackageScan(registry);
    }

    private void loadProperties() {
        //配置文件
        PropertySource propertySource = configCalss.getAnnotation(PropertySource.class);
        if (propertySource != null && propertySource.value() != null) {
            String[] pros = propertySource.value();
            for (String pro : pros) {
                InputStream resource = Thread.currentThread().getContextClassLoader().getResourceAsStream(pro);
                try {
                    properties.load(resource);
                } catch (IOException e) {
                    logger.error(pro + "配置文件找不到,", e);
                }
            }
        }
    }

    private void initConfig() throws IllegalAccessException, InstantiationException {
        Object config = configCalss.newInstance();

        //配置文件赋值
        Field[] fields = configCalss.getDeclaredFields();
        TokenHandler tokenHandler = content -> {
            String property = properties.getProperty(content);
            return property == null ? "" : property;
        };
        GenericTokenParser genericTokenParser = new GenericTokenParser("${", "}", tokenHandler);
        for (Field field : fields) {
            Value annotation = field.getAnnotation(Value.class);
            if (annotation != null) {
                String value = annotation.value();
                if (value != null) {
                    String parse = genericTokenParser.parse(value);

                    if(parse == null || parse.isEmpty()){
                        if(annotation.required()){
                            throw new RuntimeException(field.getName() + ",属性不存在");
                        }
                        continue;
                    }

                    field.setAccessible(true);

                    Class<?> type = field.getType();
                    if (type.isAssignableFrom(int.class) || type.isAssignableFrom(Integer.class)) {
                        field.set(config, Integer.parseInt(parse));
                    } else if (type.isAssignableFrom(short.class) || type.isAssignableFrom(Short.class)) {
                        field.set(config, Short.parseShort(parse));
                    } else if (type.isAssignableFrom(long.class) || type.isAssignableFrom(Long.class)) {
                        field.set(config, Long.parseLong(parse));
                    } else if (type.isAssignableFrom(String.class)) {
                        field.set(config, parse);
                    }
                }
            }
        }
        this.config = config;
    }

    private void initBeanDefinitonInConfig(Consumer<BeanDefinition> registry) {
        //处理在config中创建的bean
        Method[] declaredMethods = configCalss.getDeclaredMethods();
        //有参数的创建bean就延迟
        for (Method method : declaredMethods) {
            Bean bean = method.getAnnotation(Bean.class);
            if (bean == null) {
                continue;
            }
            Parameter[] parameters = method.getParameters();
            BeanDefinition beanDefinition = null;
            if (parameters != null && parameters.length > 0) {
                // if(BeanDefinitionProcess.class.isAssignableFrom(method.getReturnType())){
                //     throw new IllegalArgumentException(method  + ",生成的" + BeanDefinitionProcess.class.getName()
                //         + "不支持注入属性");
                // }
                //需要依赖
                List<DependInfo> dependInfos = new ArrayList<>(parameters.length);
                for (int i = 0; i < parameters.length; i++) {
                    Parameter parameter = parameters[i];
                    Autowired autowired = parameter.getAnnotation(Autowired.class);

                    if (autowired == null) {
                        throw new IllegalArgumentException(method + ",参数" + parameter.getName() + "未使用@Autowired注解");
                    }

                    DependInfo dependInfo = new DependInfo() {
                        @Override
                        public void setDependValue(Object source, Object value) {

                        }

                        @Override
                        public String getName() {
                            if(autowired.name().isEmpty()){
                                return null;
                            }
                            return autowired.name();
                        }

                        @Override
                        public Class<?> getType() {
                            if (getName() == null) {
                                return parameter.getType();
                            }
                            return null;
                        }

                        @Override
                        public boolean required() {
                            return autowired.required();
                        }
                    };

                    dependInfos.add(dependInfo);
                }

                beanDefinition = new BeanDefinition() {
                    @Override
                    public Class<?> getBeanClass() {
                        return method.getReturnType();
                    }

                    @Override
                    public List<String> getBeanNames() {
                        return Arrays.asList(bean.value());
                    }

                    @Override
                    public List<DependInfo> getDependsOn() {
                        return new ArrayList<>();
                    }

                    @Override
                    public FactoryBean getFactoryBean() {
                        FactoryBean factoryBean = new FactoryBean() {
                            private Object obj;

                            @Override
                            public Object getObject() {
                                if (obj != null) {
                                    return obj;
                                }
                                try {
                                    Object[] objects = new Object[dependInfos.size()];
                                    for (int i = 0; i < dependInfos.size(); i++) {
                                        DependInfo dependInfo = dependInfos.get(i);
                                        Object object = getBeanOrCreate(dependInfo);
                                        objects[i] = object;
                                    }

                                    obj = method.invoke(config, objects);
                                } catch (Exception e) {
                                    throw new RuntimeException(e);
                                }
                                return obj;
                            }

                            @Override
                            public Class<?> getObjectType() {
                                return getObject().getClass();
                            }
                        };
                        return factoryBean;
                    }
                };
            } else {
                beanDefinition = new BeanDefinition() {
                    @Override
                    public Class<?> getBeanClass() {
                        return method.getReturnType();
                    }

                    @Override
                    public List<String> getBeanNames() {
                        return Arrays.asList(bean.value());
                    }

                    @Override
                    public List<DependInfo> getDependsOn() {
                        return new ArrayList<>();
                    }

                    @Override
                    public FactoryBean getFactoryBean() {
                        FactoryBean factoryBean = new FactoryBean() {
                            private Object obj;

                            @Override
                            public Object getObject() {
                                if (obj != null) {
                                    return obj;
                                }
                                try {
                                    obj = method.invoke(config, null);
                                } catch (Exception e) {
                                    throw new RuntimeException(e);
                                }
                                return obj;
                            }

                            @Override
                            public Class<?> getObjectType() {
                                return getObject().getClass();
                            }
                        };
                        return factoryBean;
                    }
                };
            }
            registry.accept(beanDefinition);
        }

    }


    private void initBeanDefinitonInpackageScan(Consumer<BeanDefinition> registry) {
        ComponentScan componentScan = configCalss.getAnnotation(ComponentScan.class);
        if (componentScan == null || componentScan.value() == null) {
            return;
        }

        ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
        resolverUtil.find(new ResolverUtil.AnnotatedWith(Service.class), componentScan.value());
        Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
        for (Class<?> mapperClass : mapperSet) {
            Service service = mapperClass.getAnnotation(Service.class);

            BeanDefinition beanDefinition = new BeanDefinition() {
                private List<DependInfo> dependInfos = null;
                @Override
                public Class<?> getBeanClass() {
                    return mapperClass;
                }

                @Override
                public List<String> getBeanNames() {
                    return Arrays.asList(service.value());
                }

                @Override
                public List<DependInfo> getDependsOn() {
                    if(dependInfos != null){
                        return dependInfos;
                    }else {
                        dependInfos = new ArrayList<>();
                    }
                    Field[] fields = mapperClass.getDeclaredFields();
                    for (Field field : fields) {
                        Autowired autowired = field.getAnnotation(Autowired.class);
                        if(autowired == null){
                            continue;
                        }

                        field.setAccessible(true);
                        DependInfo dependInfo = new DependInfo(){
                            @Override
                            public void setDependValue(Object source, Object value) {
                                try {
                                    field.set(source,value);
                                } catch (IllegalAccessException e) {
                                    throw new RuntimeException(e);
                                }
                            }

                            @Override
                            public String getName() {
                                if(autowired.name().isEmpty()){
                                    return null;
                                }
                                return autowired.name();
                            }

                            @Override
                            public Class<?> getType() {
                                return field.getType();
                            }

                            @Override
                            public boolean required() {
                                return autowired.required();
                            }
                        };
                        dependInfos.add(dependInfo);
                    }

                    return dependInfos;
                }

                @Override
                public FactoryBean getFactoryBean() {
                    return null;
                }
            };

            registry.accept(beanDefinition);
        }
    }




}
